#include <ptgui/ptgui.h>
#include <ptgui/driver.h>
#include <SDL/sdl.h>

#include <stdio.h>
#include <stdlib.h>

void sdl_screen_update(ptgui_rect_t *rect);
rt_uint8_t *sdl_get_framebuffer(void);
void sdl_set_pixel(ptgui_color_t *c, rt_base_t x, rt_base_t y);
void sdl_get_pixel(ptgui_color_t *c, rt_base_t x, rt_base_t y);
void sdl_draw_hline(ptgui_color_t *c, rt_base_t x1, rt_base_t x2, rt_base_t y);
void sdl_draw_vline(ptgui_color_t *c, rt_base_t x, rt_base_t y1, rt_base_t y2);
void sdl_draw_raw_hline(rt_uint8_t *pixel, rt_base_t x1, rt_base_t x2, rt_base_t y);

struct ptgui_graphic_driver _ptgui_scr_driver =
{
	2,
	480,
	640,
	sdl_screen_update,
	sdl_get_framebuffer,
	sdl_set_pixel,
	sdl_get_pixel,
	sdl_draw_hline,
	sdl_draw_vline,
	sdl_draw_raw_hline
};

SDL_Surface *_ptgui_sdl_screen;

void sdl_driver_init()
{
	/* set video driver for VC++ debug */
	_putenv("SDL_VIDEODRIVER=windib");

	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO) < 0) {
		fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
		exit(1);
	}

	_ptgui_sdl_screen = SDL_SetVideoMode(_ptgui_scr_driver.width, _ptgui_scr_driver.height, 16,
										 SDL_SWSURFACE | SDL_DOUBLEBUF);
	if (_ptgui_sdl_screen == NULL) {
		fprintf(stderr, "Couldn't set 640x480x16 video mode: %s\n", SDL_GetError());
		exit(1);
	}

	SDL_WM_SetCaption("ptGUI Simulation", NULL);
	// SDL_ShowCursor(SDL_DISABLE);
}

void sdl_screen_update(ptgui_rect_t *rect)
{
#if 1
	SDL_UpdateRect(_ptgui_sdl_screen, rect->x1, rect->y1, rect->x2 - 1, rect->y2 - 1);
#else
	SDL_UpdateRect(_ptgui_sdl_screen, 0, 0, _ptgui_scr_driver.width, _ptgui_scr_driver.height);
#endif
	return;
}

rt_uint8_t * sdl_get_framebuffer()
{
	return _ptgui_sdl_screen->pixels;
}

void sdl_set_pixel(ptgui_color_t *c, rt_base_t x, rt_base_t y)
{
	unsigned char *p;
	int bpp = _ptgui_sdl_screen->format->BytesPerPixel;
	unsigned int pixel = SDL_MapRGBA(_ptgui_sdl_screen->format, (rt_uint8_t)PTGUI_RGB_R(*c), (rt_uint8_t)PTGUI_RGB_G(*c), (rt_uint8_t)PTGUI_RGB_B(*c), (rt_uint8_t)PTGUI_RGB_A(*c));

	// Make sure we're not drawing off the screen
	if ((x >= _ptgui_scr_driver.width) || (y >= _ptgui_scr_driver.height)) {
		return;
	}

	if (SDL_MUSTLOCK(_ptgui_sdl_screen)) {
		if (SDL_LockSurface(_ptgui_sdl_screen) < 0) {
			fprintf(stderr, "Can't lock screen: %s\n", SDL_GetError());
			return;
		}
	}

	/* Here p is the address to the pixel we want to set */
	p = (unsigned char*)_ptgui_sdl_screen->pixels + y * _ptgui_sdl_screen->pitch + x * bpp;
	switch (bpp) {
	case 1:
		*p = pixel;
		break;

	case 2:
		*(unsigned short*)p = pixel;
		break;

	case 3:
		if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
			p[0] = (pixel >> 16) & 0xff;
			p[1] = (pixel >> 8) & 0xff;
			p[2] = pixel & 0xff;
		}
		else {
			p[0] = pixel & 0xff;
			p[1] = (pixel >> 8) & 0xff;
			p[2] = (pixel >> 16) & 0xff;
		}
		break;

	case 4:
		*(unsigned int*)p = pixel;
		break;
	}

	if (SDL_MUSTLOCK(_ptgui_sdl_screen)) {
		SDL_UnlockSurface(_ptgui_sdl_screen);
	}
}

void sdl_get_pixel(ptgui_color_t *c, rt_base_t x, rt_base_t y)
{
	rt_uint8_t r,
			   g,
			   b,
			   a;
	int bpp = _ptgui_sdl_screen->format->BytesPerPixel;
	unsigned char *p = (unsigned char *)_ptgui_sdl_screen->pixels + y *_ptgui_sdl_screen->pitch + x *bpp;
	unsigned int pixel;

	// Make sure we're not drawing off the screen
	if ((x >= _ptgui_scr_driver.width) || (y >= _ptgui_scr_driver.height)) {
		return;
	}

	if (SDL_MUSTLOCK(_ptgui_sdl_screen)) {
		if (SDL_LockSurface(_ptgui_sdl_screen) < 0) {
			fprintf(stderr, "Can't lock screen: %s\n", SDL_GetError());
			return;
		}
	}

	switch (bpp) {
	case 1:
		pixel = *p;
		break;

	case 2:
		pixel = *(unsigned short*)p;
		break;

	case 3:
		if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
			pixel = p[0] << 16 | p[1] << 8 | p[2];
		}
		else {
			pixel = p[0] | p[1] << 8 | p[2] << 16;
		}
		break;

	case 4:
		pixel = *(unsigned int*)p;
		break;

	default:
		break;
	}

	r = (rt_uint8_t)PTGUI_RGB_R(*c);
	g = (rt_uint8_t)PTGUI_RGB_G(*c);
	b = (rt_uint8_t)PTGUI_RGB_B(*c);
	a = (rt_uint8_t)PTGUI_RGB_A(*c);

	SDL_GetRGBA(pixel, _ptgui_sdl_screen->format, &r, &g, &b, &a);

	if (SDL_MUSTLOCK(_ptgui_sdl_screen)) {
		SDL_UnlockSurface(_ptgui_sdl_screen);
	}
}

void sdl_draw_hline(ptgui_color_t *c, rt_base_t x1, rt_base_t x2, rt_base_t y)
{
	unsigned char *p;
	int bpp = _ptgui_sdl_screen->format->BytesPerPixel;
	unsigned int pixel = SDL_MapRGBA(_ptgui_sdl_screen->format, (rt_uint8_t)PTGUI_RGB_R(*c), (rt_uint8_t)PTGUI_RGB_G(*c), (rt_uint8_t)PTGUI_RGB_B(*c), (rt_uint8_t)PTGUI_RGB_A(*c));
	rt_base_t x;

	if (SDL_MUSTLOCK(_ptgui_sdl_screen)) {
		if (SDL_LockSurface(_ptgui_sdl_screen) < 0) {
			fprintf(stderr, "Can't lock screen: %s\n", SDL_GetError());
			return;
		}
	}

	/* Here p is the address to the pixel we want to set */
	p = (unsigned char*)_ptgui_sdl_screen->pixels + y * _ptgui_sdl_screen->pitch + x1 * bpp;
	for (x = x1; x < x2; x ++) {
		switch (bpp) {
		case 1:
			*p = pixel;
			p ++;
			break;

		case 2:
			*(unsigned short*)p = pixel;
			p += 2;
			break;

		case 3:
			if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
				p[0] = (pixel >> 16) & 0xff;
				p[1] = (pixel >> 8) & 0xff;
				p[2] = pixel & 0xff;
			}
			else {
				p[0] = pixel & 0xff;
				p[1] = (pixel >> 8) & 0xff;
				p[2] = (pixel >> 16) & 0xff;
			}

			p += 3;
			break;

		case 4:
			*(unsigned int*)p = pixel;
			p += 4;
			break;
		}
	}

	if (SDL_MUSTLOCK(_ptgui_sdl_screen)) {
		SDL_UnlockSurface(_ptgui_sdl_screen);
	}
}

void sdl_draw_vline(ptgui_color_t *c, rt_base_t x, rt_base_t y1, rt_base_t y2)
{
	unsigned char *p;
	int bpp = _ptgui_sdl_screen->format->BytesPerPixel;
	unsigned int pixel = SDL_MapRGBA(_ptgui_sdl_screen->format, (rt_uint8_t)PTGUI_RGB_R(*c), (rt_uint8_t)PTGUI_RGB_G(*c), (rt_uint8_t)PTGUI_RGB_B(*c), (rt_uint8_t)PTGUI_RGB_A(*c));
	rt_base_t y;

	if (SDL_MUSTLOCK(_ptgui_sdl_screen)) {
		if (SDL_LockSurface(_ptgui_sdl_screen) < 0) {
			fprintf(stderr, "Can't lock screen: %s\n", SDL_GetError());
			return;
		}
	}

	/* Here p is the address to the pixel we want to set */
	p = (unsigned char*)_ptgui_sdl_screen->pixels + y1 * _ptgui_sdl_screen->pitch + x * bpp;
	for (y = y1; y < y2; y ++) {
		switch (bpp) {
		case 1:
			*p = pixel;
			break;

		case 2:
			*(unsigned short*)p = pixel;
			break;

		case 3:
			if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
				p[0] = (pixel >> 16) & 0xff;
				p[1] = (pixel >> 8) & 0xff;
				p[2] = pixel & 0xff;
			}
			else {
				p[0] = pixel & 0xff;
				p[1] = (pixel >> 8) & 0xff;
				p[2] = (pixel >> 16) & 0xff;
			}
			break;

		case 4:
			*(unsigned int*)p = pixel;
			break;
		}

		p += _ptgui_sdl_screen->pitch;
	}

	if (SDL_MUSTLOCK(_ptgui_sdl_screen)) {
		SDL_UnlockSurface(_ptgui_sdl_screen);
	}
}

void sdl_draw_raw_hline(rt_uint8_t *pixel, rt_base_t x1, rt_base_t x2, rt_base_t y)
{
	unsigned char *p;
	int bpp = _ptgui_sdl_screen->format->BytesPerPixel;
	unsigned int pitch;

	if (SDL_MUSTLOCK(_ptgui_sdl_screen)) {
		if (SDL_LockSurface(_ptgui_sdl_screen) < 0) {
			fprintf(stderr, "Can't lock screen: %s\n", SDL_GetError());
			return;
		}
	}

	/* Here p is the address to the pixel we want to set */
	p = (unsigned char*)_ptgui_sdl_screen->pixels + y * _ptgui_sdl_screen->pitch + x1 * bpp;

	pitch = (x2 - x1) * bpp;
	memcpy(p, pixel, pitch);

	if (SDL_MUSTLOCK(_ptgui_sdl_screen)) {
		SDL_UnlockSurface(_ptgui_sdl_screen);
	}
}